/**
 *
 */
package com.onstar.cn.map;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.graphics.Color;
import android.graphics.Paint;
import android.os.Message;
import android.util.SparseArray;
import android.widget.LinearLayout;

import com.amap.api.maps2d.AMap;
import com.amap.api.maps2d.model.BitmapDescriptor;
import com.amap.api.maps2d.model.BitmapDescriptorFactory;
import com.amap.api.maps2d.model.LatLngBounds;
import com.amap.api.services.core.AMapException;
import com.amap.api.services.core.LatLonPoint;
import com.amap.api.services.route.DrivePath;
import com.amap.api.services.route.DriveRouteResult;
import com.amap.api.services.route.DriveStep;
import com.amap.api.services.route.RouteSearch;
import com.amap.api.services.route.RouteSearch.DriveRouteQuery;
import com.onstar.cn.map.common.Constants;
import com.onstar.cn.map.common.DriveMode;
import com.onstar.cn.map.common.DriveSegmentInfo;
import com.onstar.cn.map.common.ExceptionType;
import com.onstar.cn.map.common.Language;
import com.onstar.cn.map.common.MapException;
import com.onstar.cn.map.common.PoiItemInfo;
import com.onstar.cn.map.core.OGeoPoint;
import com.onstar.cn.map.core.impl.OGeoPointImpl;
import com.onstar.cn.map.listener.ORouteHandlerListener;
import com.onstar.cn.map.route.MyRouteOverlay;
import com.onstar.cn.map.route.RouteOverlay;
import com.onstar.cn.map.util.AMapServicesUtil;
import com.onstar.cn.map.util.MapUtil;
import com.onstar.cn.map.util.MapViewHelper;
import com.onstar.cn.map.util.MessageHandler;

/**
 * @author Qiying Gong
 * @Created 2013-4-12
 */
public final class MapRoute {

    private RouteOverlay routeOverlay;
    private MyRouteOverlay myRouteOverlay;
    private DriveRouteResult routeResult;
    private List<DriveSegmentInfo> mDriveSegmentResultList;
    private String routeDistance;
    private String routeTime;
    private Paint mPaint;

    private BitmapDescriptor wayIcon;
    private BitmapDescriptor startIcon;
    private BitmapDescriptor endIcon;

    private OMapActivity mMapActivity;
    private AMap mapView;
    private boolean isTraffic;
    private OGeoPoint startLoc;
    private OGeoPoint endLoc;

    private LinearLayout startLayout;
    private LinearLayout endLayout;

    private MessageHandler msgHandler;
    private RouteSearch routeSearch = null;
    private int driveMode = DriveMode.DrivingFastRoad;
    private Language lang;
    private MapViewHelper mapViewHelper;

    private SparseArray<DriveRouteResult> modeArrays;

    public MapRoute(OMapActivity mMapActivity, AMap mapView) {
        super();
        this.mMapActivity = mMapActivity;
        this.mapView = mapView;
        mapViewHelper = MapViewHelper.getInstance();
        msgHandler = new MessageHandler(mapView);
        routeSearch = new RouteSearch(mMapActivity.getApplicationContext());
        mDriveSegmentResultList = new ArrayList<DriveSegmentInfo>();
        modeArrays = new SparseArray<>();
    }

    /**
     * Can't calculate route if distance less than 5m
     *
     * @param routeStartPoiItemInfo
     * @param routeEndPoiItemInfo
     * @param hour                  the unit of hour, currently only support "鐏忓繑妞?"
     * @param minutes               the unit of minutes, currently only support "閸掑棝鎸?"
     * @return the thread id for current calculation
     */
    public long caculateRoute(final PoiItemInfo routeStartPoiItemInfo, final PoiItemInfo routeEndPoiItemInfo,
                              String hour, String minutes) {

        long id = calculateRoute(routeStartPoiItemInfo, routeEndPoiItemInfo, hour, minutes, false);

        return id;
    }

    /**
     * Can't calculate route if distance less than 5m
     *
     * @param routeStartPoiItemInfo start point
     * @param routeEndPoiItemInfo   end point
     * @param hour                  the unit of hour, currently only support "鐏忓繑妞?"
     * @param minutes               the unit of minutes, currently only support "閸掑棝鎸?"
     * @return the thread id for current calculation
     */
    public long caculateTempRoute(final PoiItemInfo routeStartPoiItemInfo, final PoiItemInfo routeEndPoiItemInfo,
                                  String hour, String minutes) {
        long id = calculateRoute(routeStartPoiItemInfo, routeEndPoiItemInfo, hour, minutes, true);

        return id;
    }

    /**
     * @param hour
     * @param minutes
     */
    private void generateSegmentInfo(String hour, String minutes) {
        routeResult = modeArrays.get(driveMode);
        if (routeResult != null && routeResult.getPaths().size() > 0) {
            DrivePath route = routeResult.getPaths().get(0);
            int routeStepCount = route.getSteps().size();
            mDriveSegmentResultList.clear();
            DriveSegmentInfo driveSegmentInfo = null;
            // int consumeSeconds = 0;
            // float distance = 0;

            for (int index = 0; index < routeStepCount; index++) {
                DriveStep segment = route.getSteps().get(index);
                // consumeSeconds += segment.getDuration();
                // distance += segment.getDistance();

                driveSegmentInfo = new DriveSegmentInfo();
                driveSegmentInfo.setStepedDescription(segment.getInstruction());
                driveSegmentInfo.setAction(segment.getAction());
                driveSegmentInfo.setActionDescription(segment.getAssistantAction());
                driveSegmentInfo.setConsumeTime(MapUtil.convertToHumanTime(segment.getDuration(), hour, minutes));
                driveSegmentInfo.setRoadName(segment.getRoad());
                driveSegmentInfo.setLength(MapUtil.getFriendlyLength(segment.getDistance(), this.lang));

                mDriveSegmentResultList.add(driveSegmentInfo);
                // if (driveSegmentInfoPrevious != null) {
                // stepedDescription = "濞岋拷
                // + driveSegmentInfoPrevious.getRoadName() + "鐞涘矂鈹?"
                // + driveSegmentInfoPrevious.getLength() + "缁拷
                // + segment.getActionDescription();
                // driveSegmentInfoPrevious
                // .setStepedDescription(stepedDescription);
                // mDriveSegmentResultList.add(driveSegmentInfoPrevious);
                // }
                // if (index == routeStepCount - 1) {
                // stepedDescription = "濞岋拷 + driveSegmentInfo.getRoadName()
                // + "鐞涘矂鈹?" + driveSegmentInfo.getLength() + "缁啿鍩屾潏鍓ф窗閻ㄥ嫬婀?";
                // driveSegmentInfo.setStepedDescription(stepedDescription);
                // mDriveSegmentResultList.add(driveSegmentInfo);
                // }
            }

            calculateDistHours(route.getDistance(), route.getDuration(), hour, minutes);
            this.calculateStartEnd(routeResult);
        }
    }

    /**
     * @param routeStartPoiItemInfo
     * @param routeEndPoiItemInfo
     * @throws Exception
     * @throws AMapException
     */
    private long calculateRoute(final PoiItemInfo routeStartPoiItemInfo, final PoiItemInfo routeEndPoiItemInfo,
                                String hours, String minutes, boolean tempRoute) {
        long id = System.currentTimeMillis();
        LatLonPoint startRoutePoint = new LatLonPoint(routeStartPoiItemInfo.getLatitude(),
                routeStartPoiItemInfo.getLongitude());
        LatLonPoint endRoutePoint = new LatLonPoint(routeEndPoiItemInfo.getLatitude(),
                routeEndPoiItemInfo.getLongitude());

        final RouteSearch.FromAndTo fromAndTo = new RouteSearch.FromAndTo(startRoutePoint, endRoutePoint);

        if (!tempRoute) {
            MapViewHelper.getInstance().clearMapOverlays(mapView);
            MapViewHelper.getInstance().clearRouteOverlay(mapView, routeOverlay);
        }
        ExecutorService exec = Executors.newSingleThreadExecutor();
        exec.execute(new CalculateThread(id, fromAndTo, hours, minutes));
        exec.shutdown();

        return id;
    }


    /**
     * Calculate distance and hours based on route results.
     *
     * @param hour
     * @param minutes
     * @param consumeHour
     * @param consumeMinutes
     */
    private void calculateDistHours(float distance, long consumeTime, String hour, String minutes) {
        this.routeTime = MapUtil.convertToHumanTime(consumeTime, hour, minutes);
        this.routeDistance = MapUtil.getFriendlyLength(distance, this.lang);
    }

    /**
     * Show route result/shape on the map view
     */
    public void showRoute(boolean drawDefault) {
        if (routeResult != null) {
            configRouteOverlays(drawDefault, false);

            if (this.myRouteOverlay != null) {
                this.myRouteOverlay.zoomToSpan();
            }
        }
    }

    /**
     * Show temp route result/shape on the map view
     */
    public void showTempRoute(boolean drawDefault) {
        if (routeResult != null) {
            configRouteOverlays(drawDefault, true);
        }
    }

    /**
     * @param drawDefault
     */
    private void configRouteOverlays(boolean drawDefault, boolean isTemp) {
        this.startIcon = BitmapDescriptorFactory.fromAsset("start_w.png");
        this.endIcon = BitmapDescriptorFactory.fromAsset("end_w.png");
        this.wayIcon = BitmapDescriptorFactory.fromAsset("route_coner_q.png");
        createRoutePaint(isTemp);
        if (drawDefault) {
            configRouteOverlay(isTemp);
        } else {
            if (routeOverlay != null) {
                routeOverlay.removeFromMap();
            }
            routeOverlay = new RouteOverlay(mMapActivity.getApplicationContext(), this.mapView);
            routeOverlay.setRoute(routeResult);

            if (!isTemp) {
                routeOverlay.setMarkerIcons(startIcon, endIcon, wayIcon);
            } else {
                routeOverlay.setMarkerIcons(wayIcon, wayIcon, wayIcon);
            }
            routeOverlay.addMarkerLine();
            LatLngBounds.Builder boxBuilder = LatLngBounds.builder();
            boxBuilder.include(AMapServicesUtil.convertToLatLng(routeResult.getStartPos()));
            boxBuilder.include(AMapServicesUtil.convertToLatLng(routeResult.getTargetPos()));
            LatLngBounds boundBox = boxBuilder.build();
            mapViewHelper.zoomToSpan(mapView, boundBox);
        }
    }

    /**
     *
     */
    private void configRouteOverlay(boolean isTemp) {
        DrivePath route = routeResult.getPaths().get(0);
        MapViewHelper.getInstance().clearRouteOverlay(mapView, routeOverlay);
        if (myRouteOverlay != null) {
            myRouteOverlay.removeFromMap();
        }
        myRouteOverlay = new MyRouteOverlay(mMapActivity.getApplicationContext(), mapView, route, this.startLoc.getAMapPt(),
                this.endLoc.getAMapPt());
        myRouteOverlay.setRouteMarkers(startIcon, endIcon, wayIcon);
        myRouteOverlay.setRouteColor(mPaint.getColor());

        myRouteOverlay.addToMap();

    }

    /**
     *
     */
    private void createRoutePaint(boolean isTemp) {
        if (mPaint == null) {
            mPaint = new Paint();
            mPaint.setColor(Color.BLUE);
            mPaint.setStrokeWidth(6);
            mPaint.setStyle(Paint.Style.STROKE);
        }
        if (!isTemp && isTraffic) {
            mPaint.setARGB(155, 0, 0, 255);
        }
    }

    /**
     * @param route
     */
    private void calculateStartEnd(DriveRouteResult route) {
        this.startLoc = new OGeoPointImpl(route.getStartPos().getLatitude(), route.getStartPos().getLongitude());
        this.endLoc = new OGeoPointImpl(route.getTargetPos().getLatitude(), route.getTargetPos().getLongitude());
    }

    public String getRouteDistance() {
        return routeDistance;
    }

    public String getRouteTime() {
        return routeTime;
    }

    public List<DriveSegmentInfo> getmDriveSegmentResultList() {
        return mDriveSegmentResultList;
    }

    protected RouteOverlay getRouteOverlay() {
        return routeOverlay;
    }

    public OGeoPoint getStartLoc() {
        return startLoc;
    }

    public OGeoPoint getEndLoc() {
        return endLoc;
    }

    public void setTraffic(boolean isTraffic) {
        this.isTraffic = isTraffic;
    }

    public void registerListener(ORouteHandlerListener listener) {
        msgHandler.registerRouteListener(listener);
    }

    public void removeListener() {
        msgHandler.removeRouteListener();
    }

    /**
     * @return the startLayout
     */
    public LinearLayout getStartLayout() {
        return startLayout;
    }

    /**
     * @param startLayout the startLayout to set
     */
    public void setStartLayout(LinearLayout startLayout) {
        this.startLayout = startLayout;
    }

    /**
     * @return the endLayout
     */
    public LinearLayout getEndLayout() {
        return endLayout;
    }

    /**
     * @param endLayout the endLayout to set
     */
    public void setEndLayout(LinearLayout endLayout) {
        this.endLayout = endLayout;
    }

    /**
     * @return the driveMode
     */
    public int getDriveMode() {
        return driveMode;
    }

    /**
     * @param driveMode the driveMode to set
     */
    public void setDriveMode(int driveMode) {
        this.driveMode = driveMode;
    }

    public void setRouteIcons(int startIcon, int endIcon, int wayPointIcon) {
        this.startIcon = BitmapDescriptorFactory.fromResource(startIcon);
        this.endIcon = BitmapDescriptorFactory.fromResource(endIcon);
        this.wayIcon = BitmapDescriptorFactory.fromResource(wayPointIcon);
    }

    /**
     * @param lang the lang to set
     */
    public void setLang(Language lang) {
        this.lang = lang;
    }

    /**
     * Calculate route
     *
     * @author Qiying Gong
     * @Created 2013-5-23
     */
    class CalculateThread implements Runnable {

        private RouteSearch.FromAndTo fromTo;
        private long id;
        private String hours;
        private String minutes;

        public CalculateThread(long id, RouteSearch.FromAndTo fromTo, String hours, String minutes) {
            this.id = id;
            this.fromTo = fromTo;
            this.hours = hours;
            this.minutes = minutes;
        }

        @Override
        public void run() {
            try {
                if (driveMode != DriveMode.DrivingFastRoad && driveMode != DriveMode.DrivingSaveMoney && driveMode != DriveMode.DrivingShortDistance) {
                    driveMode = DriveMode.DrivingFastRoad;
                }
                DriveRouteQuery query = new DriveRouteQuery(fromTo, driveMode, null, null, "");
                routeResult = routeSearch.calculateDriveRoute(query);

                /**
                 * different map route result for 3 sort options
                 */
                if (modeArrays.get(driveMode) == null) {
                    routeResult = routeSearch.calculateDriveRoute(query);
                    modeArrays.put(driveMode,routeResult);
                }else {
                    routeResult = modeArrays.get(driveMode);
                }
                generateSegmentInfo(hours, minutes);
                msgHandler.sendMessage(Message.obtain(msgHandler, Constants.ROUTE_COMPLETE, id));

            } catch (Exception e) {
                Exception ex = new Exception(e.getMessage(), e);
                MapException exp = new MapException(id, e.getMessage(), ex, ExceptionType.ROUTE_SEARCH_ERR);
                msgHandler.sendMessage(Message.obtain(msgHandler, Constants.ROUTE_SEARCH_ERROR, exp));
            } catch (java.lang.OutOfMemoryError e1) {
                e1.printStackTrace();
                Exception ex = new Exception(e1.getMessage(), e1);
                MapException exp = new MapException(id, e1.getMessage(), ex, ExceptionType.ROUTE_SEARCH_ERR);
                msgHandler.sendMessage(Message.obtain(msgHandler, Constants.ROUTE_SEARCH_ERROR, exp));
            }

        }

    }
}
